/**
 * 
 */
package edu.cmu.vlis.wassup.coordinator;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import java.util.Set;


/**
 * Coordinator class which coordinates all the actions in the
 * system. Currently works only on a single node. Will be 
 * implemented as primary-backup later on. 
 */
public class Coordinator {

	Set<Node> liveNodes;
	List<Node> allNodes;
	List<Process> allProcesses;
	private int nodeCounter;
	private Random random;
	private int idx = 0;

	public Coordinator() {

		loadProperties();
		this.liveNodes = new HashSet<Node>();
		this.allNodes = new ArrayList<Node>();
		this.allProcesses = new ArrayList<Process>();
		this.nodeCounter = -1;
		random = new Random();     
	}

	public static void main(String[] args) {


		Coordinator coord = new Coordinator();
		coord.initialize();
		while(true){
			coord.pollNodes();
			coord.pollProcesses();
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	private void initialize(){

		String[] ipAddressess = System.getProperty("workerNodes").split(",");

		for (String ipAdd : ipAddressess){
			Node n = new Node(ipAdd.trim(),50000);
			this.allNodes.add(n);
			if(n.startSocket())
				this.liveNodes.add(n);            
		}
        Process p;
		String[] names = System.getProperty("Ids").split(",");
		for(int i=0; i< names.length; i++){
			boolean alive = false;
			for(int k = 0;k<names.length;k++) {
				for(int j=12000;j<12010;j++) {
					p = new Process(allNodes.get(k%ipAddressess.length),j,names[k]);
					p.startSocket();
					if(p.isAlive()) {   
						alive = true;
						this.allProcesses.add(p);
						break;
					}
					/*else {
						//p.closeSocket();
					}*/
				}
			}
			if(!alive) {	
				Process p = new Process(allNodes.get(i%ipAddressess.length),this.randomPort(),names[i]);
				p.startSocket();
				this.allProcesses.add(p);
			}
		}
		System.out.println("Coordinator initialized..");
	}

	private int randomPort(){

		int randomPorts[] = new int[]{12000,12001,12002,12003,12004,12005,12006,12007,12008,12009,12010,12011};
		/*int randomPort = random.nextInt(49999);
		while(randomPort < 10001 || randomPort > 49999)
			randomPort = random.nextInt();
		return randomPort;*/
		return randomPorts[idx++];
	}
	private void pollProcesses(){

		for(Process p: this.allProcesses){
			System.out.println("Pinging: "+p.getHost().getIpAdd()+":"+p.getPort());
			if(!p.isAlive()){
				System.err.printf("Process at %s:%d %s is dead.",p.getHost().getIpAdd(),p.getPort(),p.getName());
				System.out.println();
				Node liveNode = getLiveNode();
				if(liveNode == null){
					System.err.println("Not able to connect to any live node. Process wont start anywhere");
					continue;
				}
				int newPort = this.randomPort();
				boolean resp = liveNode.startRemoteProcess(p.getName(),newPort);
				if(resp){
					p.setHost(liveNode);
					p.setPort(newPort);
					p.startSocket();
					System.out.println("Process started successfully at: "+liveNode.getIpAdd()+":"+p.getPort()+":"+p.getName());

					try {
						Thread.sleep(2000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}

					StringBuilder restartMsg = new StringBuilder("Restart");
					for(Process process : this.allProcesses){
						if(process.getName().equalsIgnoreCase("upcoming") || process.getName().equalsIgnoreCase("userevent"))
							restartMsg.append(":"+process.getHost().getIpAdd());                        
					}


					for(Process process : this.allProcesses){
						if(!(process.getName().equalsIgnoreCase("upcoming") || process.getName().equalsIgnoreCase("userevent"))){
							boolean r = process.sendRestartMsg(restartMsg.toString());
							if(r)
								System.out.println("Message sent to: "+process.getHost().getIpAdd()+":"+process.getPort());
							else
								System.out.println("Failed to send Message to: "+process.getHost().getIpAdd()+":"+process.getPort());
						}

					}

				}
			}
			else
				System.out.println("Process is alive at: "+p.getHost().getIpAdd()+":"+p.getPort());
		}
	}

	private Node getLiveNode(){

		if(this.liveNodes.isEmpty())
			return null;

		this.nodeCounter ++;
		return this.liveNodes.toArray(new Node[0])[this.nodeCounter%this.liveNodes.size()];

	}

	private void pollNodes(){

		List<Node> deadNodes = new ArrayList<Node>(); 
		for(Node n : this.allNodes){
			System.out.println("Pinging node: "+n.getIpAdd());
			if(!n.isAlive()){
				System.out.printf("Node  %s is dead.",n.getIpAdd());
				System.out.println();
				if(!n.startSocket())
					deadNodes.add(n);
				else
					this.liveNodes.add(n);
			}
			else
				System.out.println("Node is alive: "+n.getIpAdd());
		}
		this.liveNodes.removeAll(deadNodes);
	}

	private void loadProperties(){
		Properties props = new Properties();
		try {
			props.load(new FileInputStream("wassup/src/properties"));
			System.getProperties().putAll(props);

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}
